package org.mozhu.mboot.core.config;

import lombok.extern.slf4j.Slf4j;
import org.mozhu.mboot.core.config.database.DatabasePropertySource;
import org.mozhu.mboot.core.config.support.LogbackInitializing;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;

@Slf4j
public class DefaultPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
    public final static String EXTERNAL_CONFIGURATION_FILE_NAME = "config.properties";
    public final static String EXTERNAL_CONFIGURATION_LOCAL_FILE_NAME = "local.properties";
    private FileSystemResource externalConfigResource;
    private FileSystemResource externalLocalConfigResource;
    private DatabasePropertySource databasePropertySource;
    private MemoryPropertySource memoryPropertySource;

    public DefaultPropertySourcesPlaceholderConfigurer(ConfigurableEnvironment configurableEnvironment) {
        log.info("Post-processing External File PropertySource & DatabasePropertySource");
        this.setEnvironment(configurableEnvironment);
        MutablePropertySources propertySources = configurableEnvironment.getPropertySources();
        try {
            propertySources.addLast(new ResourcePropertySource("defaultProperties", new ClassPathResource("default.properties")));
        } catch (IOException e) {
            log.error("Load default properties failed", e);
        }
        String externalConfigurationPath = getExternalConfigurationPath(configurableEnvironment);
        log.info("Load External PropertySources in directory [{}]", externalConfigurationPath);
        if (StringUtils.hasText(externalConfigurationPath)) {
            try {
                externalConfigResource = new FileSystemResource(externalConfigurationPath + File.separator + EXTERNAL_CONFIGURATION_FILE_NAME);
                ResourcePropertySource resourcePropertySource = new ResourcePropertySource("externalConfigProperties", externalConfigResource);
                propertySources.addFirst(resourcePropertySource);
                log.info("Add External PropertySource config.properties [{}]", externalConfigResource.getPath());
            } catch (FileNotFoundException e) {
                log.warn("External config.properties file not found");
            } catch (IOException e) {
                log.error("Load external config properties failed", e);
            }
        }

        databasePropertySource = new DatabasePropertySource("databaseProperties", configurableEnvironment);
        //TODO make database propertySource optional
        propertySources.addFirst(databasePropertySource);

        memoryPropertySource = new MemoryPropertySource("memoryPropertySource", new HashMap<>());
        propertySources.addFirst(memoryPropertySource);

        if (StringUtils.hasText(externalConfigurationPath)) {
            try {
                externalLocalConfigResource = new FileSystemResource(externalConfigurationPath + File.separator + EXTERNAL_CONFIGURATION_LOCAL_FILE_NAME);
                ResourcePropertySource resourcePropertySource = new ResourcePropertySource("externalLocalProperties", externalLocalConfigResource);
                propertySources.addFirst(resourcePropertySource);
                log.info("Add External PropertySource local.properties [{}]", externalLocalConfigResource.getPath());
            } catch (FileNotFoundException e) {
                log.warn("External local.properties file not found");
            } catch (IOException e) {
                log.error("Load external local config properties failed", e);
            }
        }
    }

    private String getExternalConfigurationPath(ConfigurableEnvironment configurableEnvironment) {
        String applicationKey = configurableEnvironment.getProperty("application.key");
        log.info("Find application.key: " + applicationKey);
        if (applicationKey == null) {
            return null;
        }
        return configurableEnvironment.getProperty(applicationKey);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        super.postProcessBeanFactory(beanFactory);
        new LogbackInitializing(this.getAppliedPropertySources());
    }

    public void setProperty(String name, String value) {
        if (databasePropertySource != null) {
            log.info("Update property[{}] of DatabasePropertySource to [{}]", name, value);
            databasePropertySource.setProperty(name, value);
        } else if (memoryPropertySource != null) {
            log.info("Update property[{}] of MemoryPropertySource to [{}]", name, value);
            memoryPropertySource.setProperty(name, value);
        } else {
            log.info("Unable to update property[{}] to [{}]", name, value);
        }
    }

}
